
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <math.h>

#include "objtypes.h"
#include "helpers.h"

Obj *
get_list_elem_by_keyword( List * list, const Id * keyword )
{
    List *  l = list;
    for( ; l != 0; l = l->cdr )
    {
        if( OBJ_ID == l->car->type && 0 == idcmp(keyword, (Id *)l->car) )
        {
            return 0 != l->cdr ? l->cdr->car : 0;
        }
    }

    return 0;
}


int
cmp_ids_namespaces( const Id * lhs, const Id * rhs )
{
    if( lhs->ns != 0 && rhs->ns != 0 )
    {
        return idcmp( lhs->ns, rhs->ns );
    }
    
    // if element has no namespace - it considered less in
    // comparison with those that has
    if( lhs->ns == 0 && rhs->ns != 0 )
    {
        return -1;
    }

    if( lhs->ns != 0 && rhs->ns == 0 )
    {
        return 1;
    }

    return 0;
}

int idcmp( const Id * lhs, const Id * rhs )
{
    int result = lhs->hash - rhs->hash;
    if( result == 0 )
    {
        return cmp_ids_namespaces( lhs, rhs );
    }

    return result;
}

int listcmp( const List * l, const List * r )
{    
    for( ;; l = l->cdr, r = r->cdr )
    {
        int result = objcmp( l->car, r->car );
        if( result != 0 )
        {
            return result;
        }

        /* test end of loop condition */
        if( l->cdr == 0 || r->cdr == 0 )
        {
            if( l->cdr == r->cdr )
            {
                return 0;
            }
            else if( l->cdr == 0 )
            {
                return -1;
            }

            return 1;
        }
    }
}

int objcmp( const Obj * lhs, const Obj * rhs )
{
    /* compare objects that might be null pointers */
    if( lhs == 0 || rhs == 0 )
    {
        if( lhs == rhs )
        {
            return 0;
        }
        else if( lhs == 0 ) /* null pointer object considered as less than non-null one */
        {
            return -1;
        }

        return 1;
    }

    /* compare non-null objects different types, considering the following:
     * NIL < T < ID < NUM < STR < LIST
     */
    assert(
        (lhs->type == OBJ_NIL || lhs->type == OBJ_T || lhs->type == OBJ_ID || lhs->type == OBJ_INT_NUM || 
        lhs->type == OBJ_DBL_NUM || lhs->type == OBJ_STRING || lhs->type == OBJ_LIST) &&
        (rhs->type == OBJ_NIL || rhs->type == OBJ_T || rhs->type == OBJ_ID || rhs->type == OBJ_INT_NUM || 
        rhs->type == OBJ_DBL_NUM || rhs->type == OBJ_STRING || rhs->type == OBJ_LIST)
        );

    switch( lhs->type )
    {
    case OBJ_NIL:
        switch( rhs->type )
        {
        case OBJ_NIL:
            return 0;
        default:
            return -1;
        }
        break;

    case OBJ_T:
        switch( rhs->type )
        {
        case OBJ_NIL:
            return 1;
        case OBJ_T:
            return 0;
        default:
            return -1;
        }
        break;

    case OBJ_ID:
        switch( rhs->type )
        {
        case OBJ_NIL: case OBJ_T:
            return 1;
        
        case OBJ_ID:
            return idcmp( (const Id *)lhs, (const Id *)rhs );
        
        default:
            return -1;
        }
        break;

    case OBJ_INT_NUM:
        switch( rhs->type )
        {
        case OBJ_NIL: case OBJ_T: case OBJ_ID:
            return 1;

        case OBJ_INT_NUM:
            {
                int diff = ((const IntNum *)lhs)->val - ((const IntNum *)rhs)->val;
                if( diff < 0 )
                {
                    return -1;
                }
                else if( diff > 0 )
                {
                    return 1;
                }

                return 0;
            }

        case OBJ_DBL_NUM:
            {
                double diff = (double)((const IntNum *)lhs)->val - ((const DblNum *)rhs)->val;
                if( diff < 0.0 )
                {
                    return -1;
                }
                else if( diff > 0.0 )
                {
                    return 1;
                }

                return 0;
            }

        default:
            return -1;
        }
        break;

    case OBJ_DBL_NUM:
        switch( rhs->type )
        {
        case OBJ_NIL: case OBJ_T: case OBJ_ID:
            return 1;

        case OBJ_INT_NUM:
            {
                double diff = ((const DblNum *)lhs)->val - (double)((const IntNum *)rhs)->val;
                if( diff < 0.0 )
                {
                    return -1;
                }
                else if( diff > 0.0 )
                {
                    return 1;
                }

                return 0;
            }

        case OBJ_DBL_NUM:
            {
                double diff = ((const DblNum *)lhs)->val - ((const DblNum *)rhs)->val;
                if( diff < 0.0 )
                {
                    return -1;
                }
                else if( diff > 0.0 )
                {
                    return 1;
                }

                return 0;
            }

        default:
            return -1;
        }
        break;

    case OBJ_STRING:
        switch( rhs->type )
        {
        case OBJ_NIL: case OBJ_T: case OBJ_ID: case OBJ_INT_NUM: case OBJ_DBL_NUM:
            return 1;

        case OBJ_STRING:
            return strcmp( ((const Str *)lhs)->str, ((const Str *)rhs)->str );

        default:
            return -1;
        }
        break;

    case OBJ_LIST:
        switch( rhs->type )
        {
        case OBJ_NIL: case OBJ_T: case OBJ_ID: case OBJ_INT_NUM: case OBJ_DBL_NUM: case OBJ_STRING:
            return 1;

        case OBJ_LIST:
            return listcmp( (const List *)lhs, (const List *)rhs );
        }
        break;
    }

    assert( !"should never came here" );
    return -1;
}

static void
print_id_ns( Id * id, FILE * f )
{
    if( id->ns != 0 )
    {
        print_id_ns( id->ns, f );
    }

    fputs( id->str, f );
    fputc( ':', f );
}

void print_obj( Obj * obj, FILE * fout )
{
    if( obj == 0 )
    {
        return;
    }

    switch( obj->type )
    {
    case OBJ_NIL:
        fputs( "NIL", fout );
        break;

    case OBJ_T:
        fputs( "T", fout );
        break;

    case OBJ_ID:
        {
            Id *  id = (Id *)obj;
            Id *  ns = id->ns;

            // expand namespaces
            if( ns != 0 )
            {
                print_id_ns( ns, fout );
            }

            // print id itself
            fputs( ((Id *)obj)->str, fout );
        }
        break;

    case OBJ_LIST:
        {
            List * l = (List *)obj;

            fputs( "( ", fout );

            for( ; l != 0; l = l->cdr )
            {
                print_obj( l->car, fout );
                fputc( ' ', fout );
            }

            fputc( ')', fout );
        }
        break;

    case OBJ_INT_NUM:
        fprintf( fout, "%d", ((IntNum *)obj)->val );
        break;

    case OBJ_DBL_NUM:
        fprintf( fout, "%f", ((DblNum *)obj)->val );
        break;

    case OBJ_STRING:
        fputc( '"', fout );
        fputs( ((Str *)obj)->str, fout );
        fputc( '"', fout );
        break;

    default:
        /* it is not known how to stringify such an object, so a kind of exception should be thrown */
        assert( !"an undefined object has been passed" );
        break;
    }
}
